Последние изменения - Поиск:

edit SideBar

УстройствоОбъектаВПамяти

Что такое объект

В данной главе под объектом понимается нечто, что может быть записано в переменную. Например, число, массив, запись, "OBJECT". Как устроены процедуры, я сейчас не знаю, поэтому о них речь не будет идти.

Где может жить объект

Объект может:

  • жить самостоятельно на куче (например, такова запись, выделенная с помощью указателя и NEW)
  • находиться на стеке (например, такова локальная переменная типа запись)
  • находиться в статической памяти (например, такова глобальная переменная типа запись, но не факт, что это не считается кучей).
  • быть вложен в другой объект (например, запись, являющаяся элементом массива записей)

Чем ниже в списке, тем меньше информации можно узнать, обладая только адресом объекта.

Зависимость структуры объекта от места его выделения

Рантайм-среда экономит ресурсы. Она создаёт полные заголовки только для объектов, созданных с помощью NEW. Поэтому структура объекта одного и того же типа может отличаться в зависимости от того, создан ли объект на куче или в другом месте (на стеке, в стат. памяти или внутри другого объекта). Обратите внимание, что если объект(гость) вложен в другой объект(хозяин) и хозяин выделен с помощью NEW, то гость, хоть и живёт на куче, но не имеет полных заголовков!

Данные по отрицательным смещениям

Данные о типе и структуре объекта часто находятся по отрицательным смещениям относительно адреса объекта.

Следствие для указателей

Принципиально невозможно получить указатель на статическую запись или массив, которые не были выделены с помощью NEW. Даже если получить такой указатель обманом с помощью ADDRESSOF, то этот указатель не будет взаимозаменяем с указателем на тот же тип, выделенный на куче. В принципе такой указатель можно было бы ввести, но он не был бы надёжным, т.к. не было бы гарантий по существованию объекта во время обращения к нему, которые имеются в случае NEW и сборщика мусора. Нужно ещё покопать на тему указателей с атрибутами UNSAFE и UNTRACED.

Что нужно, чтобы однозначно определить тип объекта?

Для того, чтобы узнать тип объекта, достаточно одного из двух:

  • если к объекту приделаны метаданные, то достаточно знать его адрес
  • если к объекту не приделаны метаданные, то нужен массив байт Module.refs и смещение в нём refsoffset. По этой информации можно однозначно определить тип.

Где собрана информация об устройстве объекта?

Эта информация неявно присутствует в генераторе кода, компиляторе, линковщике, а также в модулях, связанных с рефлексией и отладкой. Я собрал эту информацию в модуле PodrobnajaPechatq.Mod (раньше назывался Generic.PodrobnajaPechatq.Mod), с помощью которого можно напечатать почти любой объект в деталях, но и там эта информация представлена не в слишком ясном виде.

Список примерно такой:

  • Reflection.Mod (раньше назывался Generic.Reflection.Mod)
  • Heaps.Mod (структуры блоков, а также ф-ии NewArr и NewRec)
  • Modules.Mod (раньше назывался Generic.Modules.Mod)
  • Не всё ещё указано!

Структура объектов разных видов

Структура безструктурного блока памяти

Такой блок выделяется, если известно, что в нём нет подлежащих отслеживанию указателей. Он состоит из HeapBlockDesc и DataBlockDesc, при этом:

systemBlock.heapBlock := Nil
systemBlock.typeDesc := systemBlockTag; (* константа для всех таких блоков*)
dataBlock.typeDesc := NilVal; (* это намекает на то, что этот блок - именно из таких *)
dataBlock.heapBlock := systemBlock; (* а это позволяет удостовериться *)
systemBlock.dataAdr := dataBlockAdr;
systemBlock.size := blockSize;
p := dataBlock; (* возвращаем указатель на dataBlock (собственно на данные) *)

Структура массива, выделенного на куче

Действуем по аналогии с записью и смотрим в исходник NewArr. Массив, внутри которого нет указателей, выделяется с помощью SYSTEM.NEW и его тип нельзя узнать - это всегда просто кусок памяти без структуры. Т.е. для его вывода нужно получить и разобрать описание его типа.

Если в элементе массива есть указатели, то он имеет следующую структуру:


BlockHeaderSize* = 2 * AddressSize;

HeapBlockDesc* = RECORD
        heapBlock {FICTIVE =HeapBlockOffset}: ADDRESS;
        typeDesc {FICTIVE =TypeDescOffset}: POINTER {UNSAFE} TO StaticTypeDesc;
        mark: LONGINT;
        dataAdr-: ADDRESS;
        size-: SIZE;
        nextMark {UNTRACED}: HeapBlock;
END;

ArrayBlockDesc = RECORD  (HeapBlockDesc)

DataBlockDesc*= RECORD
        heapBlock {FICTIVE =HeapBlockOffset}: POINTER {UNSAFE} TO HeapBlockDesc;
        typeDesc {FICTIVE =TypeDescOffset}: POINTER {UNSAFE} TO StaticTypeDesc;
END;

ArrayDataBlockDesc*= RECORD (DataBlockDesc)
        numElems: SIZE;
        current: ADDRESS; (* unused *)
        first: ADDRESS;
END;

StaticTypeDesc* = RECORD
        info- {FICTIVE =TypeDescOffset}: TypeInfo;
        recSize-: SIZE;
        pointerOffsets* {UNTRACED}: PointerOffsets;
END;

PointerOffsets = POINTER TO ARRAY OF SIZE;

(* Модуль Heaps *)
TypeInfo*= POINTER{UNSAFE} TO TypeInfoDesc;
TypeInfoDesc = RECORD
        descSize-: LONGINT;
        sentinel-: LONGINT;     (* = MPO-4 *)
        tag-: ADDRESS; (* pointer to static type descriptor, only used by linker and loader *)
        flags-: SET;
        mod-: ADDRESS; (* module; В Modules.TypeDesc имеет тип Module *)
        name*: ARRAY 32 OF CHAR;
        (* В Modules есть такой же тип Modules.TypeDesc и в нём ещё одно поле
        refsOffset: SIZE; *)

END;


Итого - ArrayBlockDesc, за ним - ArrayDataBlockDesc, за ним - данные
(*
Начинается значение типа ArrayBlockDesc
arrayBlockAdr - 2| arrayBlock.heapBlock : не инициализируем
-1| arrayBlock.typeDesc == SYSTEM.TYPECODE (ArrayBlockDesc) - одинаков для всех массивов
arrayBlockAdr + 0 | arrayBlock.mark - что-то для GC?
1 | arrayBlock.dataAdr : ADDRESS (адрес, помеченный как dataBlockAdr)
2 | arrayBlock.size - общий размер всего блока
3 | arrayBlock.nextMark : HeapBlock - не инициализируем
<пустота - выравнивание куска от arrayBlock.heapBlock до кратного ArrayAlignment>

Начинается значение типа ArrayDataBlockDesc
dataBlockAdr - 2 | dataBlock.heapBlock -> arrayBlockAdr
-1 | dataBlock.typeDesc -> elemTag (ОГО!)
dataBlockAdr + 0 | numElems - сюда указывает указатель на массив.
1 | current(unused)
2 | first -> указывает на первый элемент массива (= firstElem)
N | массив размерностей (вроде для обычных массивов всегда 1, даже для многомерных
<пустота - выравнивание куска от dataBlockAdr + 0 до кратного ArrayAlignment>
firstElem | начало данных, размера numElemes * elemSize
*)

Структура записи, выделенной на куче

Имеется в виду запись, выделенная с помощью NEW

BlockHeaderSize* = 2 * AddressSize;
HeapBlockOffset* = - 2 * AddressSize;
TypeDescOffset* = - AddressSize;
(* ... *)
HeapBlock* = POINTER TO HeapBlockDesc;  (* base object of all heap blocks *)
HeapBlockU = POINTER {UNSAFE} TO HeapBlockDesc; (* base object of all heap blocks *)
HeapBlockDesc* = RECORD
        heapBlock {FICTIVE =HeapBlockOffset}: ADDRESS;
        typeDesc {FICTIVE =TypeDescOffset}: POINTER {UNSAFE} TO StaticTypeDesc;
        mark: LONGINT;
        dataAdr-: ADDRESS;
        size-: SIZE;
        nextMark {UNTRACED}: HeapBlock;
END;
(* ... *)
RecordBlockU = POINTER {UNSAFE} TO RecordBlockDesc;
RecordBlockU = POINTER {UNSAFE} TO RecordBlockDesc;
RecordBlockDesc = RECORD  (HeapBlockDesc)
(* ... *)
DataBlockU = POINTER {UNSAFE} TO DataBlockDesc;
DataBlockDesc*= RECORD
        heapBlock {FICTIVE =HeapBlockOffset}: POINTER {UNSAFE} TO HeapBlockDesc;
        typeDesc {FICTIVE =TypeDescOffset}: POINTER {UNSAFE} TO StaticTypeDesc;
END;
(* Устройство записи можно выяснить из процесса её создания. Вся работа должна происходить внутри блокировки, см. Heaps.NewRec.

Запись состоит из лежащих последовательно RecordBlockDesc и DataBlockDesc, но данные занимают ещё и часть адресов, меньших RecordBlockDesc
(за счёт фиктивных полей RecordBlockDesc) и часть адресов, больших DataBlockDesc (собственно данные записи). Благодаря этому,
по корректному адресу можно узнать, является ли он записью и узнать тип этой записи.

Итого получается (для Heaps.Mod)

-2| heapBlock : ADDRESS - зачем это?
-1| typeDesc : POINTER TO StaticTypeDesc == SYSTEM.TYPECODE (RecordBlockDesc) - одинаков для всех записей
0 | mark - что-то для GC?
1 | dataAdr : ADDRESS (== +6)
2 | size
3 | nextMark : HeapBlock (для GC или что-то в этом роде)
4 | heapBlock : POINTER TO HeapBlockDesc (позволяет по объекту найти его блок. Предположительно, == +0)
5 | typeDesc : POINTER TO StaticTypeDesc == указатель на тип конкретно этой записи.
6 | начало данных == указатель в рантайм-среде указывает именно на этот адрес

*)

Структура записи, выделенной статически

По сути, статически выделенная запись (например, глобальная переменная типа запись) состоит только из своих данных. typeDesc и всего, что идёт по меньшим от него адресам, не существует, поэтому, зная только адрес объекта, узнать тип нельзя. Раньше мы обходили эту ситуацию тем, что передавали тип объекта дополнительным параметром, как-то так:

r.PechPoAdr(o, ADDRESS OF myRecordVar, SYSTEM.TYPECODE(myRecordVar));

Для этого пришлось расширить TYPECODE, чтобы оно могло принимать не только имя типа, но и выражение. Тогда возвращается для типа выражения, известного во время компиляции, описание этого типа.

Теперь предлагается пользоваться печатью стека, к-рый понимает локальную переменную, или завести запись с полем нужного вам типа, записать данные в эту запись и напечатать её. У записи есть метаданные, которые понимает Pech.

SYSTEM.NEW

Похоже, что SYSTEM.NEW выделяет кусок неструктурированной памяти. Предполагается, что в ней нет указателей. Кусок является блоком (HeapBlock) и помечается тегом типа systemBlockTag. Например, в таком блоке хранятся массивы, у которых в элементах нет указателей. Динамически узнать, что в таком блоке содержится, нельзя. Но, наверное, можно узнать об этом, если тип известен статически. Есть функции NewArr и NewRec, которые частично помогают понять, как работает NEW. Однако полное понимание работы NEW возможно только при понимании генерации кода.

Небольшой диалог из телеграма

[=
Денис Будяк, [16.05.20 13:54]
Всем привет! У меня есть значение типа ANY, на самом деле это - указатель на запись. Я могу узнать предка типа этой записи?
Я пошарил-пошарил и как-то не вижу, как это можно сделать.
Мой модуль отладочной печати в связи с этим выводит поля только от типа данной записи, но не от предков.

Единственный возможный вариант, к-рый видится - это сначала определить имя типа, потом парсить символьный файл, находить имя предка в нём, открывать тот символьный файл и т.п.

Sergey Durmanov, [16.05.20 14:10]
[In reply to Денис Будяк]
языковыми средствами в оберонах это сделать невозможно

Денис Будяк, [16.05.20 14:10]
Я не про обероны и не про языковые средства, а про A2 и про внутренние средства.

Sergey Durmanov, [16.05.20 14:10]
все предки записи указаны в дескрипторе типа

Денис Будяк, [16.05.20 14:13]
Дескриптор типа - это что? Есть Modules.TypeDesc - там нету
Есть Heaps.StaticTypeDesc - там нет
И есть Heaps.TypeInfo - там тоже нет
Есть Modules.refs, к-рые можно как-то узнать для модуля - там не попалось
Есть BaseTypes.Descriptor - там есть.
Но как его нарыть?

Sergey Durmanov, [16.05.20 14:17]
Ну так магия компилятора, оно там уложено последовательно, но такой структуры  нет

Sergey Durmanov, [16.05.20 14:17]
[In reply to Денис Будяк] Есть BaseTypes.Descriptor - там есть.
это для локфри
Править - История - Печать - Последние изменения - Поиск
Редакция от 16.05.2020 14:29